package Games;

import java.util.ArrayList;
import java.util.PriorityQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class OthelloAI implements Runnable {
	public static final int THREADS_PER_CPU = 20;
	
	public static final int DIMENSION = 8;
	public static final int EMPTY = 2;
	public static final int DEPTH = 5;

	public static final int N = 0;
	public static final int NE= 1;
	public static final int E = 2;
	public static final int SE= 3;
	public static final int S = 4;
	public static final int SW= 5;
	public static final int W = 6;
	public static final int NW= 7;

	public static final int VALUE_WIN = 50;
	public static final int VALUE_CORNER = 10;
	public static final int VALUE_SKIP = 10;
	public static final int VALUE_SIDE = 5;
	public static final int VALUE_MOBILITY = 2;
	public static final int VALUE_X = -5;

	private static PriorityQueue<Move> queue = new PriorityQueue<Move>();

	
	public static ExecutorService pool = 
		Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()*THREADS_PER_CPU);

	public static final int playerOne = 0;
	public static final int playerTwo = 1;

	private int moveth;
	private int playerth;
	private int[][] boardth;
	private int[] flipeth;
	private int freeth;
	private int depthth;

	public OthelloAI(int moveth, int playerth, int[][] boardth, int[] flipseth, int freeth, int depthth) {
		this.moveth  = moveth;
		this.playerth= playerth;
		this.boardth = boardth;
		this.flipeth = flipseth;
		this.freeth  = freeth;
		this.depthth = depthth;
	}
	
	public static int chooseMove(int player, int[][] board) {
		ArrayList<Integer> moves = new ArrayList<Integer>();
		ArrayList<int[]> flipses = new ArrayList<int[]>();
		int free=0;

		for (int zet = 0; zet < DIMENSION*DIMENSION; zet++) {
			int[] localFlips = new int[NW+1];
			if (moveOk(zet, player, board, localFlips)) {
				moves.add(zet);
				flipses.add(localFlips);
			}
			if (board[yFromMove(zet)][xFromMove(zet)]==EMPTY)
				free++;
		}

		if (moves.size()==0)
			return -1;

		queue.clear();
		for (int n = 0; n < moves.size(); n++) {
			new Thread(new OthelloAI(moves.get(n), player, board, flipses.get(n), free, DEPTH)).start();
		}
		synchronized (queue) {
			while (queue.size() < moves.size()) {
				try {
					queue.wait();
				} catch (InterruptedException e) {
					break;
				}
			}
		}
		return queue.peek().getMove();
	}

	@Override
	public void run() {
		Move m = new Move(moveth, evaluate(moveth, playerth, boardth, flipeth, freeth, depthth));
		synchronized (queue) {
			queue.add(m);
			queue.notify();
		}
	}

	private static int evaluate(int move, int player, int[][] board, int[] flips, int free, int depth) {
		int value=0;

		// pas aantal berekeningen toe om een waarde te geven aan deze zet
		if (isCorner(move))
			value+=VALUE_CORNER;
		else if (isXCorner(move, player, board))
			value+=VALUE_X;
		else if (isSide(move))
			value+=VALUE_SIDE;

		// hoeveel stenen worden door deze zet geflipt? Dit is belangrijker aan het eind.
		int aantalFlips = countFlips(flips);
		if (free < DIMENSION*DIMENSION/3)
			value+=aantalFlips*2;
		else
			value+=aantalFlips/2;

		if (depth==0)
			return value;

		int[][] newBoard = copyBoard(board);
		playMove(move, player, newBoard, flips);
		int highestValue=9999;
		boolean canDoMove = false;
		int[] scores = new int[2];
		scores[0]=0;
		scores[1]=0;
		for (int newMove = 0; newMove < DIMENSION*DIMENSION; newMove++) {
			if (newBoard[yFromMove(newMove)][xFromMove(newMove)]!=EMPTY)
				scores[newBoard[yFromMove(newMove)][xFromMove(newMove)]]++;

			int[] localFlips = new int[NW+1];
			if (!canDoMove && moveOk(newMove, player, newBoard, localFlips))
				canDoMove = true;

			if (!moveOk(newMove, otherPlayer(player), newBoard, localFlips))
				continue;

			// elke mogelijke zet die de tegenstander krijgt is ongunstig.
			// ofwel hoe minder value verlaagd wordt, hoe beter deze zet.
			value-=VALUE_MOBILITY;

			// een zet die voor de tegenstander gunstig is, is voor ons ongunstig, dus -
			int val = -evaluate(newMove, otherPlayer(player), newBoard, localFlips, free-1, depth-1);
			// de tegenstander zal de voor ons ongunstigste zet doen, vandaar < ipv > hier
			if (val < highestValue)
				highestValue = val;
		}
		if (!canDoMove && highestValue==9999) {
			if (scores[player]>scores[otherPlayer(player)])
				return VALUE_WIN;
			else
				return -VALUE_WIN;
		} else if (highestValue==9999) {
			value+=VALUE_SKIP;
		}
		return highestValue+value;
	}

	public static boolean isCorner(int move) {
		if (move==0 || move==DIMENSION-1 ||
				move==(DIMENSION-1)*DIMENSION ||
				move== DIMENSION * DIMENSION-1)
			return true;
		return false;
	}
	public static boolean isXCorner(int move, int player, int[][] board) {
		if (move == DIMENSION+1 && ( board[0][0] == EMPTY || 
				board[0][1] == EMPTY || 
				board[1][0] == EMPTY))
			return true;
		if (move == 2*DIMENSION-2 && (board[0][DIMENSION-2] == EMPTY ||
				board[0][DIMENSION-1] == EMPTY ||
				board[1][DIMENSION-1] == EMPTY))
			return true;
		if (move == DIMENSION*(DIMENSION-2)+1 && (board[DIMENSION-2][0] == EMPTY ||
				board[DIMENSION-1][0] == EMPTY ||
				board[DIMENSION-1][1] == EMPTY))
			return true;
		if (move == DIMENSION*(DIMENSION-1)-2 && (board[DIMENSION-1][DIMENSION-1] == EMPTY ||
				board[DIMENSION-2][DIMENSION-1] == EMPTY ||
				board[DIMENSION-1][DIMENSION-2] == EMPTY))
			return true;
		return false;
	}
	public static boolean isSide(int move) {
		if (xFromMove(move)==0 ||
				xFromMove(move)==DIMENSION-1 ||
				yFromMove(move)==0 ||
				yFromMove(move)==DIMENSION-1)
			return true;
		return false;
	}
	public static int countFlips(int[] flips) {
		int aantal = 0;
		for (int dir = N; dir <= NW; dir++) {
			aantal+=flips[dir];
		}
		return aantal;
	}

	public static int[][] copyBoard(int[][] board) {
		int[][] newBoard = new int[board.length][board[0].length];
		for (int n = 0; n < newBoard.length; n++) {
			System.arraycopy(board[n], 0, newBoard[n], 0, board[n].length);
		}
		return newBoard;
	}

	public static boolean moveOk(int move, int player, int[][] board, int[] flips) {
		if (move<0 || move > DIMENSION*DIMENSION-1)
			return false;

		int x = xFromMove(move);
		int y = yFromMove(move);
		if (board[y][x]!=EMPTY)
			return false;

		clearFlips(flips);
		boolean foundOnePotential = false;
		for (int i=1; i<=DIMENSION; i++) {
			if (i==2) {
				for (int dir = N; dir <= NW; dir++) {
					if (flips[dir]>0) {
						foundOnePotential = true;
						break;
					}
				}
				if (!foundOnePotential)
					return false;
			}
			doFlip(i, y-i, x  , otherPlayer(player), N , board, flips);
			doFlip(i, y-i, x+i, otherPlayer(player), NE, board, flips);
			doFlip(i, y  , x+i, otherPlayer(player), E , board, flips);
			doFlip(i, y+i, x+i, otherPlayer(player), SE, board, flips);
			doFlip(i, y+i, x  , otherPlayer(player), S , board, flips);
			doFlip(i, y+i, x-i, otherPlayer(player), SW, board, flips);
			doFlip(i, y  , x-i, otherPlayer(player), W , board, flips);
			doFlip(i, y-i, x-i, otherPlayer(player), NW, board, flips);
		}
		for (int dir = N; dir <= NW; dir++) {
			if (flips[dir]>0)
				return true;
		}
		return false;
	}
	private static void doFlip(int i, int y, int x, int other, int dir, int[][] board, int[] flips) {
		if (x<0 || x>DIMENSION-1 ||
				y<0 || y>DIMENSION-1) {
			if (flips[dir] == i-1)
				flips[dir]=0;
		} else if (board[y][x]==other && flips[dir]==i-1) {
			flips[dir]++;
		} else if (board[y][x]==EMPTY && flips[dir]==i-1) {
			flips[dir]=0;
		}
	}

	private static boolean noMovePossible(int player, int[][] board) {
		int[] flips = new int[NW+1];
		for (int n = 0; n < DIMENSION*DIMENSION; n++) {
			if ((player==0 && (moveOk(n, playerOne, board, flips) || moveOk(n, playerTwo, board, flips))) || 
					(player!=0 && moveOk(n, player, board, flips)))
				return false;
		}
		return true;
	}

	public static void playMove(int move, int player, int[][] board, int[] flips) {
		int x = xFromMove(move);
		int y = yFromMove(move);
		//		int player = nextPlayer);

		board[y][x] = player;

		int dx = 0, dy = 0;
		for (int dir = N; dir <= NW; dir++) {
			switch (dir) {
			case N:	dx= 0; dy=-1; break;
			case NE:dx=+1; dy=-1; break;
			case E:	dx=+1; dy= 0; break;
			case SE:dx=+1; dy=+1; break;
			case S:	dx= 0; dy=+1; break;
			case SW:dx=-1; dy=+1; break;
			case W:	dx=-1; dy= 0; break;
			case NW:dx=-1; dy=-1; break;
			}
			for (int i = 1; i <= flips[dir]; i++) {
				board[y+dy*i][x+dx*i]=player;
			}
		}
	}
	public static int xFromMove(int move) {
		return move%DIMENSION;
	}
	public static int yFromMove(int move) {
		return move/DIMENSION;
	}
	private static void clearFlips(int[] flips) {
		for (int dir = N; dir <= NW; dir++) {
			flips[dir]=0;
		}
	}
	public static int otherPlayer(int player) {
		return player==playerOne ? playerTwo : playerOne;
	}

	private class Move implements Comparable<Move> {

		private int move;
		private int value;

		public Move(int move, int value) {
			this.move=move;
			this.value=value;
		}
		public int getMove() {
			return move;
		}
		@Override
		public int compareTo(Move m) {
			return m.value-this.value; // omgekeerd omdat we de hoogste waarde aan de head van de queue willen
		}

	}
}